import { useCallback, useEffect, useRef, useState } from 'react';
import { apiClient, getTaskUrl, TaskStatus } from './api';

interface UseTaskSSEOptions {
  enabled?: boolean;
  // initial reconnect delay in ms
  initialDelayMs?: number;
  // max backoff cap in ms
  maxDelayMs?: number;
  // polling interval when falling back (ms)
  pollMs?: number;
  // max SSE retries before falling back to polling
  maxSseRetries?: number;
}

interface UseTaskSSEResult {
  data?: TaskStatus;
  error?: string;
  connecting: boolean;
}

/**
 * Subscribe task progress via SSE with exponential backoff reconnect (<= maxDelayMs).
 * Fallback to 1s polling when SSE is unavailable.
 */
export function useTaskSSE(taskId?: number | string, options: UseTaskSSEOptions = {}): UseTaskSSEResult {
  const { enabled = true, initialDelayMs = 1000, maxDelayMs = 30000, pollMs = 1000, maxSseRetries = 5 } = options;
  const [data, setData] = useState<TaskStatus | undefined>();
  const [error, setError] = useState<string | undefined>();
  const [connecting, setConnecting] = useState<boolean>(false);

  const esRef = useRef<EventSource | null>(null);
  const timeoutRef = useRef<number | null>(null);
  const pollRef = useRef<number | null>(null);
  const backoffRef = useRef<number>(initialDelayMs);
  const sseFailRef = useRef<number>(0);

  const clearTimers = useCallback(() => {
    if (timeoutRef.current) {
      window.clearTimeout(timeoutRef.current);
      timeoutRef.current = null;
    }
    if (pollRef.current) {
      window.clearInterval(pollRef.current);
      pollRef.current = null;
    }
  }, []);

  const closeES = useCallback(() => {
    if (esRef.current) {
      esRef.current.close();
      esRef.current = null;
    }
  }, []);

  const startPolling = useCallback((url: string) => {
    clearTimers();
    pollRef.current = window.setInterval(async () => {
      try {
        const { data } = await apiClient().get<TaskStatus>(url);
        setData(data);
        setError(undefined);
      } catch (e: any) {
        setError(e?.message || 'Polling failed');
      }
    }, pollMs) as unknown as number;
  }, [clearTimers, pollMs]);

  const connect = useCallback((url: string) => {
    if (!enabled || !taskId) return;
    if (typeof window === 'undefined' || !('EventSource' in window)) {
      // Fallback: browser doesn't support SSE
      startPolling(url);
      return;
    }

    setConnecting(true);
    try {
      const es = new EventSource(url);
      esRef.current = es;

      es.onopen = () => {
        setConnecting(false);
        backoffRef.current = initialDelayMs; // reset backoff on successful open
        sseFailRef.current = 0; // reset failure count
      };

      // default message event (rarely used in our backend)
      es.onmessage = (evt) => {
        try {
          const parsed: TaskStatus = JSON.parse(evt.data);
          setData(parsed);
          setError(undefined);
        } catch (_) {
          // Ignore non-JSON lines
        }
      };

      // named events from backend: progress/done/error
      es.addEventListener('progress', (evt: MessageEvent) => {
        try {
          const p = JSON.parse(evt.data);
          setData((prev) => ({
            ...prev,
            processed: p.processed,
            success: p.success,
            failed: p.failed,
            total: p.total,
            throughput: p.throughput,
            etaSeconds: p.etaSeconds,
          } as any));
          setError(undefined);
        } catch (_) {
          // ignore
        }
      });
      es.addEventListener('done', (evt: MessageEvent) => {
        const status = String(evt.data || 'SUCCEEDED');
        setData((prev) => ({ ...(prev as any), status }));
      });
      es.addEventListener('error', (evt: MessageEvent) => {
        setError(String(evt.data || 'SSE stream error'));
      });

      es.onerror = () => {
        setConnecting(false);
        setError('SSE connection error');
        closeES();
        sseFailRef.current += 1;
        if (sseFailRef.current >= maxSseRetries) {
          // fall back to polling
          startPolling(url);
          return;
        }
        // schedule reconnect with capped exponential backoff
        const delay = Math.min(backoffRef.current, maxDelayMs);
        timeoutRef.current = window.setTimeout(() => {
          backoffRef.current = Math.min(backoffRef.current * 2, maxDelayMs);
          connect(url);
        }, delay) as unknown as number;
      };
    } catch (e: any) {
      setConnecting(false);
      setError(e?.message || 'SSE init failed');
      // fallback to polling immediately
      startPolling(url);
    }
  }, [enabled, taskId, closeES, initialDelayMs, maxDelayMs, startPolling, maxSseRetries]);

  useEffect(() => {
    if (!enabled || !taskId) return;
    const url = getTaskUrl(taskId);
    connect(url);
    return () => {
      clearTimers();
      closeES();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [taskId, enabled]);

  return { data, error, connecting };
}
